﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace ReactiveLang
{
    partial class Expression
    {
        class LatestNotification<T> : IExpression<T>, IDisposable
        {
            Notification<T> _latest;
            Subject<T> _listeners = new Subject<T>();
            IDisposable _handle;

            public LatestNotification(IExpression<T> source)
            {
                Contract.Requires(source != null);
                _handle = source.ToNotifications().Activate(
                    x =>
                    {
                        _latest = x;
                        x.Accept(_listeners);
                    },
                    ex => { throw ex; });
            }
            public void Dispose()
            {
                _handle.Dispose();
            }

            public IDisposable Activate(IExpressionParent<T> parent)
            {
                if (_latest != null)
                    _latest.Accept(parent);
                return _listeners.Activate(parent);
            }

            public override string ToString()
            {
                if (_latest == null)
                    return "null";
                else
                    return _latest.ToString();
            }

        }

        internal class LatestValue<T> : IExpression<T>, IDisposable
        {
            IDisposable _handle;
            Subject<T> _listeners;

            public T Value { get; private set; }
            public bool HasValue { get; private set; }

            public LatestValue(IExpression<T> expression, Action<Exception> onException, bool distinct)
            {
                Contract.Requires(expression != null);
                Contract.Requires(onException != null);
                _handle = expression.Activate(
                    onValue: x =>
                    {
                        if (distinct && HasValue && EqualityComparer<T>.Default.Equals(Value, x))
                            return;
                        Value = x;
                        HasValue = true;
                        if (_listeners != null)
                            _listeners.OnValue(x);
                    }, 
                    onException : onException);
            }
            public void Dispose()
            {
                _handle.Dispose();
            }

            public IDisposable Activate(IExpressionParent<T> parent)
            {
                if (HasValue)
                    parent.OnValue(Value);
                if (_listeners == null)
                    _listeners = new Subject<T>();
                return _listeners.Activate(parent);
            }
        }

        internal static LatestValue<T> Latest<T>(this IExpression<T> expression, Action<Exception> onException, bool distinct = false)
        {
            Contract.Requires(expression != null);
            Contract.Requires(onException != null);
            return new LatestValue<T>(expression, onException, distinct);
        }
        internal static LatestValue<T> Latest<T, T2>(this IExpression<T> expression, IExpressionParent<T2> grandParent, bool distinct = false)
        {
            Contract.Requires(expression != null);
            Contract.Requires(grandParent != null);
            return Latest(expression, grandParent.OnException, distinct);
        }
    }
}
